#!/usr/bin/env python
"""
Axe Interpreter

An interpreter for programs written in the Axe Parser language for computers.

Author: Michael Lee (<michael.lee.0x2a@gmail.com>)
License: GNU Lesser GPL
"""

# System libraries
import sys
import operator

# 3rd party libraries
from ply import lex as lex
from ply import yacc as yacc
import pygame

# My libraries
import io
import axelex
from axelex import tokens
import meta
from meta import ExitException

_debug = False
_temp_counter = 0


_CONSTANTS = {'AZ_VARS':35254,
              'R_VARS':33701,
              'CONSTS':40000,
              'L1':34540,
              'L2':35386,
              'L3':39026,
              'L4':33445,
              'L5':34056,
              'L6':37696,
              'TEMP':2000}

_VAR_ORDER = ['A', 'B', 'C', 'D', 'E',
              'F', 'G', 'H', 'I', 'J',
              'K', 'L', 'M', 'N', 'O', 
              'P', 'Q', 'R', 'S', 'T', 
              'U', 'V', 'W', 'X', 'Y', 
              'Z', 'THETA']

def debug(*args):
    if _debug:
        print args
    return



def get_keybindings():
    temp = io.get_resource('keybindings.config')
    keybindings = {}
    for computer_name in temp.keys():
        calc_code = int(temp[computer_name], 10)
        if calc_code == 0:
            continue    # If the code is set to zero, the key is unassigned.
        
        computer_code = getattr(pygame, computer_name)
        if calc_code not in keybindings:
            keybindings[calc_code] = [computer_code]
        else:
            keybindings[calc_code].append(computer_code)
    keybindings[0] = [0]
    return keybindings

_keybindings = get_keybindings()

# See the ply documentation.
precedence = (
    ('left', 'LT', 'LE', 'EQ', 'NE', 'GE', 'GT'),   # Lowest precedence
    ('left', 'ADD', 'SUB', 'MUL', 'DIV', 'MOD')    #
    #('right', 'UMINUS')                             # Unary minus (highest precedence)
)


## Program ##
def p_program(p):
    ''' program : program block
                | block'''
    debug('p_program',list(p))
    if len(p) == 3:
        p[0].append(p[2])
    elif len(p) == 2:
        p[0] = ['PROGRAM', p[1]]

def p_block_1(p):
    '''block : block block'''
    debug('p_statement_1', list(p))
    p[0] = ['BLOCK']
    p[0].extend(p[1][1:])
    p[0].extend(p[2][1:])
    return

def p_block_2(p):
    '''block : node
             | meta'''
    debug('p_block_2', list(p))
    p[0] = ['BLOCK', p[1]]
    return

def p_block_3(p):
    '''block : newline block'''
    debug('p_block_3', list(p))
    p[0] = p[2]
    return


## Nodes ##
def p_node_while(p):
    '''node : WHILE expression newline block END newline'''
    debug('p_block_while', list(p))
    p[0] = ['WHILE', ['CONDITION', p[2]], p[4]]
    return

def p_node_repeat(p):
    '''node : REPEAT expression newline block END newline'''
    debug('p_block_repeat', list(p))
    p[0] = ['REPEAT', ['CONDITION', p[2]], p[4]]
    return

def p_node_for_1(p):
    '''node : FOR LPAREN pointer COMMA expression COMMA expression RPAREN newline block END newline'''
    debug('p_block_for_1', list(p))
    
    var = p[3]
    start = p[5]
    end = p[7]
    block = p[10]
    p[0] = ['FOR', ['LOOP', var, start, end], block]
    return

def p_node_for_2(p):
    '''node : FOR LPAREN expression RPAREN newline block END newline'''
    debug('p_block_for_2', list(p))
    # TODO: Make this function genuinely faster then the original.
    
    global _temp_counter
    var = _CONSTANTS['TEMP'] + _temp_counter
    _temp_counter += 2
    start = 0
    end = p[3]
    block = p[6]
    
    p[0] = ['FOR', ['LOOP', ['POINTER', var, 2], start, end], block]    
    return

def p_node_if(p):
    '''node : IF expression newline block END newline'''
    debug('p_block_if', list(p))
    p[0] = ['IF', ['CONDITION', p[2]], p[4]]
    return

def p_node_else(p):
    '''node : IF expression newline block ELSE newline block END newline'''
    debug('p_block_else', list(p))
    p[0] = ['ELSEIF', ['CONDITION', p[2], p[4]], ['CONDITION', 1, p[7]]]
    return

def p_node_elseif(p):
    '''node : elseif END newline'''
    debug('p_node_elseif', list(p))
    p[0] = p[1]
    return

def p_elseif_seed(p):
    '''elseif : IF expression newline block ELSEIF expression newline block'''
    debug('p_elseif_seed', list(p))
    p[0] = ['ELSEIF', ['CONDITION', p[2], p[4]], ['CONDITION', p[6], p[8]]]
    return

def p_elseif_continued(p):
    '''elseif : elseif ELSEIF expression newline block'''
    debug('p_elseif_continued', list(p))
    prev = p[1]
    prev.append(['CONDITION', p[3], p[5]])
    p[0] = prev
    return

def p_node_expr(p):
    '''node : expression newline'''
    debug('p_node_expr', list(p))
    p[0] = p[1]
    return

def p_node_factor(p):
    '''node : factor newline'''
    debug('p_node_factor', list(p))
    p[0] = p[1]

def p_node_disp(p):
    '''node : DISP expression newline'''
    debug('p_command_disp', list(p))
    p[0] = ['DISP', p[2]]
    return

def p_node_pause(p):
    '''node : PAUSE expression newline
            | PAUSE assign newline'''
    debug('p_node_pause', list(p))
    p[0] = ['PAUSE', p[2]]
    return

## Drawing Commands ##
def p_node_draw_pxl(p):
    '''node : PXL_ON LPAREN expression COMMA expression RPAREN newline
            | PXL_OFF LPAREN expression COMMA expression RPAREN newline
            | PXL_CHANGE LPAREN expression COMMA expression RPAREN newline'''
    debug('p_node_draw_pxl_on', list(p))
    p[0] = ['DRAW_' + p[1].upper(), ['COORD', p[3], p[5]]]
    return

def p_node_draw_dispgraph(p):
    '''node : DISPGRAPH newline'''
    debug('p_node_draw_dispgraph', list(p))
    p[0] = ['DRAW_' + p[1].upper(), 37696, 2]
    return

def p_node_draw_clrdraw(p):
    '''node : CLRDRAW newline'''
    debug('p_node_draw_clrdraw', list(p))
    p[0] = ['DRAW_' + p[1].upper(), 37696, 2]
    return

def p_node_draw_rect(p):
    '''node : RECT LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN newline
            | RECTI LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN newline'''
    debug('p_node_draw_rect', list(p))
    p[0] = ['DRAW_' + p[1].upper(), ['COORD', p[3], p[5]], ['COORD', p[7], p[9]]]
    return

def p_node_draw_line(p):
    '''node : LINE LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN newline'''
    debug('p_node_draw_line', list(p))
    p[0] = ['DRAW_' + p[1].upper(), ['COORD', p[3], p[5]], ['COORD', p[7], p[9]]]
    return

def p_node_draw_horizontal(p):
    '''node : HORIZONTAL ADD newline
            | HORIZONTAL SUB newline'''
    debug('p_node_draw_horizontal', list(p))
    p[0] = ['DRAW_' + p[1].upper(), 1 if p[2] == 'ADD' else -1, 0]
    return

def p_node_draw_vertical(p):
    '''node : VERTICAL ADD newline
            | VERTICAL SUB newline'''
    debug('p_node_draw_vertical', list(p))
    p[0] = ['DRAW_' + p[1].upper(), 0, 1 if p[2] == 'ADD' else -1]
    return

## Numbers ##

# This function must come before 'p_expression_number()' to
# ensure arithmatic evaluates from left to right.
def p_factor(p):
    '''factor : NUMBER
              | expression
              | tempexpression'''
    debug('p_factor', list(p))
    p[0] = p[1]
    return

def p_expression_increment(p):
    '''expression : pointer INCREMENT'''
    debug('p_expression_increment', list(p))
    p[0] = ['ASSIGN', ['OPERATOR', 'add', p[1], 1], p[1]]
    return

def p_expression_decrement(p):
    '''expression : pointer DECREMENT'''
    debug('p_expression_decrement', list(p))
    p[0] = ['ASSIGN', ['OPERATOR', 'sub', p[1], 1], p[1]]
    return

def p_expression(p):
    '''expression : expression operator factor'''
    debug('p_expression', list(p))
    a = p[1]
    op = p[2].lower()
    b = p[3]
    if type(a) == int and type(b) == int:
        p[0] = getattr(operator, op)(a, b)
    else:
        p[0] = ['OPERATOR', p[2].lower(), p[1], p[3]]
    return

def p_factor_2(p):
    '''factor : LPAREN factor RPAREN'''
    debug('p_factor_2', list(p))
    p[0] = p[2]
    return

def p_expression_paren(p):
    '''expression : LPAREN expression RPAREN'''
    debug('p_expression_paren', list(p))
    p[0] = p[2]
    return

def p_expression_number(p):
    '''expression : NUMBER
                  | tempexpression'''
    debug('p_expression_number', list(p))
    p[0] = p[1]
    return

def p_expression_negatives(p):
    '''factor : SUB expression'''  # See ply documentation on precedence
    debug('p_expression_negatives', list(p))
    p[0] = ['OPERATOR', 'sub', 0, p[2]]


## Assignment ##
def p_node_assign(p):
    '''node : assign newline'''
    debug('p_node_assign', list(p))
    p[0] = p[1]
    return

def p_assign_seed(p):
    '''assign : expression ASSIGN pointer'''
    debug('p_node_assign', list(p))
    p[0] = ['ASSIGN', p[1],p[3]]
    return

def p_node_assign_chain(p):
    '''assign : assign ASSIGN pointer'''
    debug('p_node_assign_chain', list(p))
    p[0] = ['ASSIGN', p[1], p[3]]
    return

def p_node_assign_chain_2(p):
    '''assign : assign operator expression ASSIGN pointer'''
    debug('p_node_assign_chain_2', list(p))
    p[0] = ['ASSIGN', ['OPERATOR', p[2].lower(), p[3], p[1]], p[5]]

## Expressions ##
def p_expression_getkey(p):
    '''tempexpression : GETKEY LPAREN expression RPAREN'''
    debug('p_expression_getkey', list(p))
    try:
        if p[3] == 0:
            p[0] = ['GETKEY', None]
        else:
            v = ['GETKEY']
            v.extend(_keybindings[p[3]])
            p[0] = v
    except KeyError:
        p[0] = 0
        # TODO: Log warning.
    return

def p_expression_rand(p):
    '''tempexpression : RAND'''
    debug('p_expression_rand', list(p))
    p[0] = ['RAND', 0]
    return

def p_expression_draw_pxl_test(p):
    '''tempexpression : PXL_TEST LPAREN expression COMMA expression RPAREN'''
    debug('p_expression_draw_pxl_test', list(p))
    p[0] = ['DRAW_' + p[1].upper(), ['COORD', p[3], p[5]]]
    return

    
## Meta ##
def p_meta_exit(p):
    '''meta : EXIT newline
            | EXIT expression newline'''
    debug('p_meta_exit', list(p))
    exit_code = 'Called @EXIT'
    if len(p) == 4:
        exit_code = p[2]
    p[0] = ['META', 'EXIT', exit_code]
    return

def p_meta_debug(p):
    '''meta : DEBUG expression newline'''
    debug('p_meta_debug', list(p))
    global _debug
    if p[2] == 0:
        _debug = False
    else:
        _debug = True
    p[0] = ['META', 'DEBUG', p[2]]
    return

def p_meta_state(p):
    '''meta : STATE newline'''
    debug('p_meta_state', list(p))
    p[0] = ['META', 'STATE']
    return

def p_meta_draw(p):
    '''meta : DRAW expression newline'''
    debug('p_meta_draw', list(p))
    p[0] = ['META', 'DRAW', p[2]]
    return

def p_meta_help(p):
    '''meta : HELP newline'''
    debug('p_meta_help', list(p))
    p[0] = ['META', 'HELP']
    return

def p_meta_about(p):
    '''meta : ABOUT newline'''
    debug('p_meta_about', list(p))
    p[0] = ['META', 'ABOUT']
    return

def p_meta_open(p):
    '''meta : OPEN STRING newline'''
    debug('p_meta_open', list(p))
    p[0] = ['META', 'OPEN', p[2]]
    return


## Pointers ##
def p_expression_pointer(p):
    '''tempexpression : pointer'''
    debug('p_expression_pointer', list(p))
    p[0] = p[1]
    return

def p_pointer(p):
    '''pointer : LBRACK expression RBRACK'''
    debug('p_pointer', list(p))
    p[0] = ['POINTER', p[2], 1]
    return

def p_pointer_2(p):
    '''pointer : LBRACK expression RBRACK RMODIFIER'''
    debug('p_pointer_2', list(p))
    p[0] = ['POINTER', p[2], 2]
    return

def p_pointer_var(p):
    '''pointer : VAR'''
    debug('p_pointer_var', list(p))
    var = p[1][4:]
    order = 2
    p[0] = ['POINTER', _CONSTANTS['AZ_VARS'] + _VAR_ORDER.index(var) * order, order]
    return

def p_expression_dereference(p):
    '''tempexpression : DEREFERENCE pointer'''
    debug('p_expression_dereference', list(p))
    p[0] = ['DEREFERENCE', p[2]]
    return

def p_expression_const(p):
    '''tempexpression : CONST'''
    debug('p_expression_const', list(p))
    p[0] = _CONSTANTS[p[1][6:]]
    return


## Misc ##
def p_newline(p):
    '''newline : COLON
               | NEWLINE
               | newline newline''' # Reduces newlines.
    debug('p_newline', list(p))
    p[0] = p[1]
    return

def p_operator(p):
    '''operator : ADD
                | SUB
                | MUL
                | DIV
                | MOD
                | LT
                | LE
                | EQ
                | NE
                | GT
                | GE
    '''
    debug('p_operator', list(p))
    p[0] = p[1]
    return

def p_id(p):
    '''node : ID'''
    p[0] = None
    return

def p_error(t):
    try:
        info = {'lineno': t.lineno, 'value':t.value}
        print "Syntax error @ line {lineno}: '{value}'".format(**info)
    except AttributeError:
        print "Syntax error."
    return

def build():
    return yacc.yacc(tabmodule='parsing_rules', write_tables=0, debug=0)



def test_parser(lexer = axelex.build(), text=''):
    parser = build()
    if text != '':
        print parser.parse(text, lexer=lexer)
    else:
        while 1:
            try:
                s = raw_input('calc > ')
                if s[-1] != '\n' or s[-1] != ':':
                    s = s + '\n'
            except (EOFError, KeyboardInterrupt, ExitException):
                print ''
                break
            if s:
                result = parser.parse(s, lexer=lexer)
                print result, '\n'


if __name__ == "__main__":
    test_parser()

